library(tidyverse)
library(ggplot2)
library(here)

here::here()
[1] "/Users/emma/Library/CloudStorage/OneDrive-SharedLibraries-IndianaUniversity/Lennon, Jay - 0000_Bueren/Projects/LifeStyle/PhageLifestyleSporulation"
### note the quartz script needs to be fixed, the headers are janky and wrong (had to be manually fixed)



ParS <- read.delim2(here("02_motifs/ParS/data/01_out_01_indiv_hmm1_ParS.tsv"))




## remove text headers
#ParS <- subset(ParS, ParS$motif_id=="ParS_BSub")
ParS$q.value<- as.numeric(ParS$q.value)
ParS$p.value<- as.numeric(ParS$p.value)
ParS$score<- as.numeric(ParS$score)



### remove any duplicate motifs (occasionally a motif will be a palindromic and hit both + and - strands) 
ParS <- ParS %>%
  group_by(sequence_name, start, stop) %>%  # group by coordinates
  slice_max(order_by = score, n = 1) %>%    # keep only the row with highest score
  ungroup()
### phi3T KY030782, spbeta AF020713

## redrock (actino phage with ParABS) GU339467, uses parS sites but of a different type than sporulation ParS

## combine phage metadata with ParS hits. Label any phage that had no ParS hits with "no_hit" in metadata
inph <- read.csv(here("00_data", "inphared_db", "14Apr2025_knownsporestatus.csv"), row.names = 1)
inph$lifestyle <- ifelse(inph$lifestyle=="Temp", "Temperate", inph$lifestyle)
inph$bac.host <- ifelse(inph$sporulation=="Spor", "Sporulating Bacillota", "Nonsporulating Bacillota")  #"Asporogenous Bacillota")
inph$bac.host <- ifelse(inph$newgtdb_Phylum=="Bacillota", inph$bac.host, "Non-Bacillota")

inph <- unite(inph, nice, c("lifestyle", "bac.host"), sep=" Phages of ", remove = FALSE )


meta.cats <- unique(select(inph, host_phage_spor, bac.host, lifestyle, nice, phage_type, sporulation))



inph <- select(inph, Accession, Host, Genome.Length..bp., gtdb_f, f_spor, newgtdb_Phylum,  host_phage_spor, phage_type, sporulation, lifestyle, nice, sporulation)
all <- merge(inph, ParS, by.x="Accession", by.y="sequence_name", all.x=TRUE, all.y=FALSE)
all$motif_id[is.na(all$motif_id)] <- "no_hit"

### create binary hit column of 1 for ParS hit, 0 if no hit
all$hit <- ifelse(all$motif_id=="ParS_BSub", 1, 0)
all$q.value[is.na(all$q.value)] <- 1

## subset for JUST BACILLUS since i used just a bacillus parS gene
#all <- subset(all, all$Host=="Bacillus")
#all <- subset(all, all$newgtdb_Phylum =="Bacillota")


### threshold testing

library(dplyr)
library(ggplot2)

# Define thresholds
thresholds <- 10^seq(-6, -1, by = 1)   # from 1e-6 to 1e-1
thresholds <- c(thresholds, 0.05, 1)      # add 0.05 explicitly if desired
results <- lapply(thresholds, function(th) {
  all %>%
    group_by(Accession, phage_type) %>%
    summarise(
      pos.ParS.hit = max(ifelse(!is.na(q.value) & q.value <= th, 1, 0)),
      .groups = "drop"
    ) %>%
    group_by(phage_type) %>%
    summarise(
      Phage_has_ParS = sum(pos.ParS.hit),
      total.phage = n(),
      .groups = "drop"
    ) %>%
    mutate(
      ParS.pos.perc = Phage_has_ParS / total.phage * 100,
      threshold = th
    )
}) %>% bind_rows()

results.fig <- merge(results, meta.cats, by="phage_type")

# Plot
ggplot(results.fig, aes(x = threshold, y = ParS.pos.perc, color = sporulation, linetype = lifestyle)) +
  geom_line(size = 1.2) +
  geom_point() +
  scale_x_log10() +  # log scale for q-values
  labs(
    x = "False Positive (q-value) Threshold",
    y = "% Phages with 1 or more ParS site"
  ) +
  theme_minimal() + labs(linetype = "Phage Lifestyle", color="Bacterial Host") +geom_vline(xintercept = 1e-4, linetype = "dotted")

NA
NA
NA
NA

#### Q FILTERING
ParS.qual <- all

ParS.qual$hit <- ifelse(ParS.qual$q.value<=1e-4, 1, 0)


## create binary list of phages w/ and w/out ParS hits
ParS.pos <- ParS.qual %>% group_by(Accession, phage_type) %>%
  summarise(total.ParS.hits = sum(hit), pos.ParS.hit = max(hit), .groups = "drop") #%>% 
ParS.pos %>% count(phage_type)

ParS.summary <- ParS.pos %>% 
  group_by(phage_type) %>% 
  summarise(
    total_ParS_hits = sum(total.ParS.hits, na.rm = TRUE),
    Phage_has_ParS = sum(pos.ParS.hit, na.rm = TRUE),
    total.phage = n(),
    .groups = "drop"
  ) %>% 
  mutate(ParS.pos.perc = Phage_has_ParS / total.phage * 100)


#ParS.hits <- ParS.hits.all
ParS.hits <- subset(ParS.qual, ParS.qual$hit==1)
#ParS.hits <- subset(ParS.hits.all, ParS.hits.all$phage_type=="Lytic_Spor")

## find center phage genome (whole genome /2)
ParS.hits$seq.mdpt <- as.numeric(ParS.hits$Genome.Length..bp.)/2

## find center of motif 
ParS.hits$motif.mdpt <- (ParS.hits$stop + ParS.hits$start )/ 2

### subtract midpoint motif from sequence midpoint to see how far away they are
ParS.hits <- ParS.hits %>%
  mutate(mdpt.align = (motif.mdpt - seq.mdpt))

ParS.hits$mdpt.align.kbp <- ParS.hits$mdpt.align/1000

#### TO GET RELATIVE motif alignmen

# Relative position as fraction of genome length
# (-0.5 = start, 0 = center, +0.5 = end)
ParS.hits$rel.mdpt <- (ParS.hits$motif.mdpt - ParS.hits$seq.mdpt) / ParS.hits$Genome.Length..bp.



# Relative position from genome start (0 to 1)
ParS.hits$rel.frac <- ParS.hits$motif.mdpt / ParS.hits$Genome.Length..bp.

# Optionally convert to percentage
ParS.hits$rel.percent <- ParS.hits$rel.frac * 100

##Set specific order for bacterial hosts to appear on graphs
ParS.hits$nice <- factor(ParS.hits$nice, levels = c('Lytic Phages of Sporulating Bacillota', 'Temperate Phages of Sporulating Bacillota', 'Lytic Phages of Nonsporulating Bacillota', 'Temperate Phages of Nonsporulating Bacillota', 'Lytic Phages of Non-Bacillota', 'Temperate Phages of Non-Bacillota' ),ordered = TRUE)


ggplot(ParS.hits, aes(x = mdpt.align.kbp, fill = phage_type)) +
  geom_histogram(binwidth = 5, position = "dodge") +
  geom_vline(xintercept = 0, linetype = "dotted") +
  scale_x_continuous(breaks = seq(-90, 90, by = 30)) +
  labs(x = "Motif distance (kb) from center of phage genome", y = "Motif count") +
  facet_wrap(~ phage_type, ncol = 2)+ ggtitle("Absolute Position of ParS Motif on Phage Genomes") + theme(legend.position="none")

ggsave(here("02_motifs/ParS/AbsoluteParSposition.png"))
Saving 6.45 x 3.99 in image

ggplot(ParS.hits, aes(x = rel.mdpt, fill = phage_type)) +
  geom_histogram(binwidth = 0.05, position = "dodge") +
  geom_vline(xintercept = 0, linetype = "dotted") +
  scale_x_continuous(breaks = seq(-0.5, 0.5, by = 0.25)) +
  labs(x = "Motif position relative to center of phage genome", y = "Motif count") +
  facet_wrap(~ phage_type, ncol = 2) + ggtitle("Relative Position of ParS Motif on Phage Genomes")+ theme(legend.position="none")

ggsave(here("02_motifs/ParS/RelativeParSposition.png"))
Saving 6.45 x 3.99 in image

statistics


ParS.bin <- ParS.pos[,c(1,2,4)]
analyze_enrichment <- function(df, ref_treatment = "Enriched") {
  # relevel the treatment factor
  df$host_phage_spor <- relevel(factor(df$phage_type), ref = ref_treatment)
  
  # logistic regression: motif presence ~ treatment
  model <- glm(pos.ParS.hit ~ phage_type, family = binomial, data = df)
  
  # estimated probabilities per treatment
  emm <- emmeans(model, ~ phage_type, type = "response")
  
  list(
    model_summary = summary(model),
    probabilities = emm,
    pairwise_tests = pairs(emm, adjust = "tukey")
  )
}
library(emmeans)
res <- analyze_enrichment(ParS.bin, ref_treatment = "Lytic_Spor")

res$model_summary     # logistic regression coefficients

Call:
glm(formula = pos.ParS.hit ~ phage_type, family = binomial, data = df)

Coefficients:
                       Estimate Std. Error z value Pr(>|z|)    
(Intercept)            -4.57600    0.09498 -48.181   <2e-16 ***
phage_typeLytic_Spor    2.71386    0.18191  14.919   <2e-16 ***
phage_typeTemp_NonSpor -0.17137    0.14990  -1.143    0.253    
phage_typeTemp_Spor     1.96261    0.20891   9.395   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 2840.5  on 20521  degrees of freedom
Residual deviance: 2621.0  on 20518  degrees of freedom
AIC: 2629

Number of Fisher Scoring iterations: 7
res$probabilities     # estimated motif probability per treatment
 phage_type      prob       SE  df asymp.LCL asymp.UCL
 Lytic_NonSpor 0.0102 0.000958 Inf   0.00847    0.0123
 Lytic_Spor    0.1345 0.018100 Inf   0.10283    0.1739
 Temp_NonSpor  0.0086 0.000989 Inf   0.00686    0.0108
 Temp_Spor     0.0683 0.011800 Inf   0.04843    0.0955

Confidence level used: 0.95 
Intervals are back-transformed from the logit scale 
res$pairwise_tests    # all pairwise comparisons
 contrast                     odds.ratio     SE  df null z.ratio p.value
 Lytic_NonSpor / Lytic_Spor       0.0663 0.0121 Inf    1 -14.919  <.0001
 Lytic_NonSpor / Temp_NonSpor     1.1869 0.1780 Inf    1   1.143  0.6627
 Lytic_NonSpor / Temp_Spor        0.1405 0.0294 Inf    1  -9.395  <.0001
 Lytic_Spor / Temp_NonSpor       17.9076 3.4700 Inf    1  14.896  <.0001
 Lytic_Spor / Temp_Spor           2.1196 0.5140 Inf    1   3.101  0.0104
 Temp_NonSpor / Temp_Spor         0.1184 0.0260 Inf    1  -9.733  <.0001

P value adjustment: tukey method for comparing a family of 4 estimates 
Tests are performed on the log odds ratio scale 
prob_df <- as.data.frame(res$probabilities)
head(prob_df)
 phage_type          prob          SE  df  asymp.LCL  asymp.UCL
 Lytic_NonSpor 0.01019108 0.000958041 Inf 0.00847480 0.01225065
 Lytic_Spor    0.13445378 0.018055002 Inf 0.10282556 0.17392461
 Temp_NonSpor  0.00859993 0.000988729 Inf 0.00686347 0.01077095
 Temp_Spor     0.06828194 0.011837698 Inf 0.04842622 0.09546218

Confidence level used: 0.95 
Intervals are back-transformed from the logit scale 
prob_df$host_phage_spor <- reorder(prob_df$phage_type, prob_df$prob)



fig.meta <- merge(prob_df, meta.cats, by="phage_type", all=TRUE)
pd <- position_dodge(width = 0.6)  # adjust width as needed

ggplot(fig.meta, aes(x = phage_type, y = prob, color = sporulation)) +
  geom_point(size = 3, position = pd) +  ylim(0,0.20)+
  geom_errorbar(aes(ymin = asymp.LCL, ymax = asymp.UCL), 
                width = 0.2, position = pd) +
  ylab("Probability of 1 or more ParS \n binding sites in phage genome") +
  xlab("Bacterial Host") +
  labs(color = "Phage Lifestyle") +
  theme_classic(base_size = 14) +
  guides(colour = guide_legend(reverse = FALSE))






ggplot(fig.meta, aes(x = phage_type, y = prob, color = lifestyle)) +
   geom_point(size = 3, position = pd) +  ylim(0,0.20)+
  geom_errorbar(aes(ymin = asymp.LCL, ymax = asymp.UCL), 
                width = 0.2, position = pd)+
  ylab("Probability of 1 or more ParS \n binding sites in phage genome") +
  xlab("Bacterial Host") +
  labs(color = "Phage Lifestyle") +
  theme_classic(base_size = 14) +
  guides(colour = guide_legend(reverse = FALSE))


## create binary list of phages w/ and w/out ParS hits
ParS.sum <- all %>% group_by(Accession, phage_type) %>%
  summarise(total.ParS.hits = sum(hit), pos.ParS.hit = max(hit), .groups = "drop") #%>% 


ggplot(ParS.pos, aes(x = factor(phage_type), fill = factor(phage_type), color=factor(phage_type), y = total.ParS.hits )) +
  geom_boxplot(binaxis = "y", stackdir = "center", position = "dodge") +  geom_jitter(width = 0.2) + ylab("Number of ParS sites / Genome") +theme(axis.text.x = element_text(angle = 50, vjust = 1, hjust = 1)) + theme(legend.position = "left") + xlab("Phyla_Spor_Lifestyle") + ggtitle("Total ParS Binding Sites per Genome")
Warning: Ignoring unknown parameters: `binaxis` and `stackdir`

ggplot(ParS.pos, aes(x = factor(phage_type), fill = factor(phage_type), color=factor(phage_type), y = total.ParS.hits )) + geom_violin() #+  geom_jitter(width = 0.2) + ylab("Number of ParS sites / Genome") +theme(axis.text.x = element_text(angle = 50, vjust = 1, hjust = 1)) + theme(legend.position = "left") + xlab("Phyla_Spor_Lifestyle") + ggtitle("Total ParS Binding Sites per Genome")




ParS_summary <- ParS.pos %>%
  group_by(phage_type) %>%
  summarise(mean_hit = mean(pos.ParS.hit, na.rm = TRUE)*100,
            se_hit = (sd(pos.ParS.hit, na.rm = TRUE)/sqrt(n())))

ParS_summary$se_hit <- ParS_summary$se_hit*100

ggplot(ParS_summary, aes(x = factor(phage_type),
                         y = mean_hit,
                         fill = factor(phage_type))) +
  geom_col(color = "black", position = "dodge") + 
  geom_errorbar(aes(ymin = mean_hit - se_hit,
                    ymax = mean_hit + se_hit),
                width = 0.2) +
  ylab("% Phage with 1+ ParS  site") +
  xlab("Phyla_Spor_Lifestyle") +
  theme(axis.text.x = element_text(angle = 50, vjust = 1, hjust = 1),
        legend.position = "none") + ggtitle("Proportion of Phages with at least 1 ParS Binding Site")




ggplot(ParS.pos, aes(x = factor(phage_type), fill = factor(phage_type), color=factor(phage_type), y = pos.ParS.hit )) +
  geom_boxplot(binaxis = "y", stackdir = "center", position = "dodge") +  geom_jitter(width = 0.2) + ylab("Number of ParS sites / Genome") +theme(axis.text.x = element_text(angle = 50, vjust = 1, hjust = 1)) + theme(legend.position = "left") + xlab("Phyla_Spor_Lifestyle") + ggtitle("Total ParS Binding Sites per Genome")
Warning: Ignoring unknown parameters: `binaxis` and `stackdir`

ggplot(ParS.pos, aes(x = factor(phage_type), fill = factor(phage_type), color=factor(phage_type), y = pos.ParS.hit )) +
  geom_violin() + geom_jitter(width = 0.2) + ylab("Number of ParS sites / Genome") +theme(axis.text.x = element_text(angle = 50, vjust = 1, hjust = 1)) + theme(legend.position = "left") + xlab("Phyla_Spor_Lifestyle") + ggtitle("Total ParS Binding Sites per Genome")



ggplot(ParS.pos, aes(x = factor(phage_type), fill = factor(phage_type), color=factor(phage_type), y = pos.ParS.hit )) +
  geom_violin()

pairwise_fisher_summary <- function(df, group_col = "group", hits_col = "hits", total_col = "total", p.adjust.method = "BH") {
  groups <- df[[group_col]]
  combs <- combn(groups, 2, simplify = FALSE)
  
  results <- data.frame(
    group1 = character(),
    group2 = character(),
    odds_ratio = numeric(),
    conf_low = numeric(),
    conf_high = numeric(),
    p.value = numeric(),
    p.adj = numeric(),
    stringsAsFactors = FALSE
  )
  
  pvals <- numeric()
  
  for (c in combs) {
    # subset the two groups
    g1 <- df[df[[group_col]] == c[1], ]
    g2 <- df[df[[group_col]] == c[2], ]
    
    # create 2x2 table
    tab <- matrix(c(
      g1[[hits_col]], g1[[total_col]] - g1[[hits_col]],
      g2[[hits_col]], g2[[total_col]] - g2[[hits_col]]
    ), nrow = 2, byrow = TRUE)
    
    rownames(tab) <- c(c[1], c[2])
    colnames(tab) <- c("Present", "Absent")
    
    ft <- fisher.test(tab)
    
    pvals <- c(pvals, ft$p.value)
    
    results <- rbind(results, data.frame(
      group1 = c[1],
      group2 = c[2],
      odds_ratio = ft$estimate,             # odds ratio
      conf_low = ft$conf.int[1],            # lower 95% CI
      conf_high = ft$conf.int[2],           # upper 95% CI
      p.value = ft$p.value,
      p.adj = NA
    ))
  }
  
  # Adjust p-values
  results$p.adj <- p.adjust(pvals, method = p.adjust.method)
  
  return(results)
}


#Pars.FISH, 

t <- pairwise_fisher_summary(
  df = ParS.summary,
  group_col = "phage_type", 
  hits_col = "Phage_has_ParS",  # number of positive hits
  total_col = "total.phage",    # total number per group
  p.adjust.method = "BH"
)

t

t$sig <- FALSE

t$sig <-ifelse(t$p.adj<0.05, TRUE, FALSE)
library(dplyr)
library(ggplot2)
library(emmeans)
library(ggsignif)

# -----------------------------
# 1️⃣ Prepare plotting dataframe
prob_df <- as.data.frame(res$probabilities)

#meta.cats <- meta.cats[,c(5,6)]

fig.meta <- merge(prob_df, meta.cats, by = "phage_type", all = TRUE)

# Set custom x-axis order
fig.meta$phage_type <- factor(
  fig.meta$phage_type,
  levels = c(
    #'Lytic_Spor', 'Lytic_NonSpor', 'Temp_Spor', 'Temp_NonSpor'
    'Lytic_Spor', 'Temp_Spor','Lytic_NonSpor',  'Temp_NonSpor'
  ),
  ordered = TRUE
)

# -----------------------------
# 2️⃣ Extract pairwise Tukey tests

pairs_df <- as.data.frame(res$pairwise_tests) %>% mutate( contrast_char = as.character(contrast), p_label = case_when( p.value < 0.001 ~ “”, p.value < 0.01 ~ ””, p.value < 0.05 ~ ””, TRUE ~ “ns” ), group1 = sapply(strsplit(contrast_char, ” / “), [, 1), group2 = sapply(strsplit(contrast_char,” / “), [, 2) )

# -----------------------------
# 3️⃣ Compute x positions and span
pairs_df <- as.data.frame(res$pairwise_tests) %>%
  mutate(
    contrast_char = as.character(contrast),
    p_label = case_when(
      p.value < 0.001 ~ "***",
      p.value < 0.01  ~ "**",
      p.value < 0.05  ~ "*",
      TRUE            ~ "ns"
    ),
    group1 = sapply(strsplit(contrast_char, " / "), `[`, 1),
    group2 = sapply(strsplit(contrast_char, " / "), `[`, 2)
  )

pairs_df <- as.data.frame(res$pairwise_tests) %>%
  mutate(
    contrast_char = as.character(contrast),
    p_label = case_when(
      p.value > 0.05            ~ "ns",                      # non-significant
      p.value < 0.001           ~ "p < 0.0001",              # very small p-values
      TRUE                       ~ paste0("p = ", sprintf("%.3f", p.value))  # others
    ),
    group1 = sapply(strsplit(contrast_char, " / "), `[`, 1),
    group2 = sapply(strsplit(contrast_char, " / "), `[`, 2)
  )




pairs_df <- pairs_df %>%
  mutate(
    x_num1 = as.numeric(factor(group1, levels = levels(fig.meta$phage_type))),
    x_num2 = as.numeric(factor(group2, levels = levels(fig.meta$phage_type))),
    span = abs(x_num1 - x_num2),
    x_pos = (x_num1 + x_num2) / 2
  ) %>%
  arrange(span)

# -----------------------------
# 4️⃣ Compute y positions for nested brackets
offset_step <- 0.01

pairs_df <- pairs_df %>%
  rowwise() %>%
  mutate(
    y_base = max(
      fig.meta$asymp.UCL[fig.meta$phage_type %in% c(group1, group2)],
      na.rm = TRUE
    )
  ) %>%
  ungroup() %>%
  arrange(span, desc(p_label)) %>%  # shorter spans lower, ns lower
  mutate(
    y_pos = y_base + (row_number() - 1) * offset_step
  )

# -----------------------------
# 5️⃣ Prepare comparisons list for ggsignif
comparisons_list <- lapply(1:nrow(pairs_df), function(i) {
  c(as.character(pairs_df$group1[i]), as.character(pairs_df$group2[i]))
})

# -----------------------------
# 6️⃣ Plot with dodge and black brackets
pd <- position_dodge(width = 0.5)

tplot <- ggplot(fig.meta, aes(x = phage_type, y = prob, color = lifestyle)) +
  geom_point(size = 3, position = pd) +
  geom_errorbar(aes(ymin = asymp.LCL, ymax = asymp.UCL), width = 0.2, position = pd) +
  ggsignif::geom_signif(
    comparisons = comparisons_list,
    annotations = pairs_df$p_label,
    y_position = pairs_df$y_pos,
    tip_length = 0.02,
    textsize = 3.5,
    color = "black"
  ) +
  ylim(0, max(pairs_df$y_pos + 0.02)) +  # extend y-axis to fit top brackets
   ylab("Probability of 1 or more ParS \n binding sites in phage genome") +
  xlab("") +
  labs(color = "Phage Lifestyle") +
  theme_classic(base_size = 14)+scale_x_discrete(
    labels = c(
      'Lytic_Spor'    = 'Lytic',
      'Temp_Spor'     = 'Temperate',
      'Lytic_NonSpor' = 'Lytic',
      'Temp_NonSpor'  = 'Temperate'
    )
  ) + theme(plot.margin = margin(10, 10, 30, 10)  # large bottom margin for labels
  ) +
  coord_cartesian(clip = "off")

tplot



spor_label <- textGrob(
  "Sporulating Host", gp = gpar(fontsize = 14, fontface = "bold"),
  x = 0.25, y = -0.13, just = "center"
)

nonspor_label <- textGrob(
  "Non-Sporulating Host", gp = gpar(fontsize = 14, fontface = "bold"),
  x = 0.75, y = -0.13, just = "center"
)


tplot_final <- tplot +
  annotation_custom(spor_label) +
  annotation_custom(nonspor_label)

tplot_final

NA
NA

pairs_df2 <- subset(pairs_df, pairs_df$contrast=="Lytic_NonSpor / Lytic_Spor" |
                      pairs_df$contrast== "Temp_NonSpor / Temp_Spor" |
                      pairs_df$contrast=="Bacillota_Lytic_NonSpor / Bacillota_Temp_NonSpor" |
                      pairs_df$contrast=="Lytic_Spor / Temp_Spor" |
                      pairs_df$contrast=="Lytic_NonSpor / Temp_NonSpor")
#Bacillota_Lytic_Spor / OtherPhyla_Lytic_NonSpor

pairs_df2 <- pairs_df2 %>%
  mutate(
    x_num1 = as.numeric(factor(group1, levels = levels(fig.meta$phage_type))),
    x_num2 = as.numeric(factor(group2, levels = levels(fig.meta$phage_type))),
    span = abs(x_num1 - x_num2),  # distance between groups
    x_pos = (x_num1 + x_num2) / 2  # center for bracket
  ) %>%
  arrange(span)  # smaller spans first

# -----------------------------
# 4️⃣ Compute y positions for nested brackets

pairs_df2 <- pairs_df2 %>%
  rowwise() %>%
  mutate(
    y_base = max(
      fig.meta$asymp.UCL[fig.meta$phage_type %in% c(group1, group2)],
      na.rm = TRUE
    )
  ) %>%
  ungroup() %>%
  mutate(
    y_pos = y_base + (row_number() - 1) * offset_step  # stack by row order (smaller span lower)
  )

# -----------------------------
# 5️⃣ Prepare comparisons list for ggsignif
comparisons_list <- lapply(1:nrow(pairs_df2), function(i) {
  c(as.character(pairs_df2$group1[i]), as.character(pairs_df2$group2[i]))
})

# -----------------------------
# 6️⃣ Plot with dodge for multiple hosts
pd <- position_dodge(width = 0.5)  # adjust width for separation of points

tplot2 <- ggplot(fig.meta, aes(x = phage_type, y = prob, color = lifestyle)) +
  geom_point(size = 3, position = pd) +
  geom_errorbar(aes(ymin = asymp.LCL, ymax = asymp.UCL), width = 0.2, position = pd) +
  ggsignif::geom_signif(
    comparisons = comparisons_list,
    annotations = pairs_df2$p_label,
    y_position = pairs_df2$y_pos,
    tip_length = 0.02,
    textsize = 3.5,
    color = "black"
  ) +
  ylim(0, max(pairs_df2$y_pos + 0.02)) +  # extend y-axis to fit top brackets
   ylab("Probability of 1 or more ParS \n binding sites in phage genome") +
  xlab("") +
  labs(color = "Phage Lifestyle") +
  theme_classic(base_size = 14)+scale_x_discrete(
    labels = c(
      'Lytic_Spor'    = 'Lytic',
      'Temp_Spor'     = 'Temperate',
      'Lytic_NonSpor' = 'Lytic',
      'Temp_NonSpor'  = 'Temperate'
    )
  ) + theme(plot.margin = margin(10, 10, 30, 10)  # large bottom margin for labels
  ) +
  coord_cartesian(clip = "off")

tplot2



spor_label <- textGrob(
  "Sporulating Host", gp = gpar(fontsize = 14, fontface = "bold"),
  x = 0.25, y = -0.13, just = "center"
)

nonspor_label <- textGrob(
  "Non-Sporulating Host", gp = gpar(fontsize = 14, fontface = "bold"),
  x = 0.75, y = -0.13, just = "center"
)


tplot_final2 <- tplot2 +
  annotation_custom(spor_label) +
  annotation_custom(nonspor_label)

tplot_final2

LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQpgYGB7cn0KbGlicmFyeShkcGx5cikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGdncHVicikKbGlicmFyeShlbW1lYW5zKQpsaWJyYXJ5KGdnc2lnbmlmKQpsaWJyYXJ5KGhlcmUpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGdyaWQpCmhlcmU6OmhlcmUoKQoKIyMjIG5vdGUgdGhlIHF1YXJ0eiBzY3JpcHQgbmVlZHMgdG8gYmUgZml4ZWQsIHRoZSBoZWFkZXJzIGFyZSBqYW5reSBhbmQgd3JvbmcgKGhhZCB0byBiZSBtYW51YWxseSBmaXhlZCkKCgoKUGFyUyA8LSByZWFkLmRlbGltMihoZXJlKCIwMl9tb3RpZnMvUGFyUy9kYXRhLzAxX291dF8wMV9pbmRpdl9obW0xX1BhclMudHN2IikpCgoKCgojIyByZW1vdmUgdGV4dCBoZWFkZXJzCiNQYXJTIDwtIHN1YnNldChQYXJTLCBQYXJTJG1vdGlmX2lkPT0iUGFyU19CU3ViIikKUGFyUyRxLnZhbHVlPC0gYXMubnVtZXJpYyhQYXJTJHEudmFsdWUpClBhclMkcC52YWx1ZTwtIGFzLm51bWVyaWMoUGFyUyRwLnZhbHVlKQpQYXJTJHNjb3JlPC0gYXMubnVtZXJpYyhQYXJTJHNjb3JlKQoKCgojIyMgcmVtb3ZlIGFueSBkdXBsaWNhdGUgbW90aWZzIChvY2Nhc2lvbmFsbHkgYSBtb3RpZiB3aWxsIGJlIGEgcGFsaW5kcm9taWMgYW5kIGhpdCBib3RoICsgYW5kIC0gc3RyYW5kcykgClBhclMgPC0gUGFyUyAlPiUKICBncm91cF9ieShzZXF1ZW5jZV9uYW1lLCBzdGFydCwgc3RvcCkgJT4lICAjIGdyb3VwIGJ5IGNvb3JkaW5hdGVzCiAgc2xpY2VfbWF4KG9yZGVyX2J5ID0gc2NvcmUsIG4gPSAxKSAlPiUgICAgIyBrZWVwIG9ubHkgdGhlIHJvdyB3aXRoIGhpZ2hlc3Qgc2NvcmUKICB1bmdyb3VwKCkKIyMjIHBoaTNUIEtZMDMwNzgyLCBzcGJldGEgQUYwMjA3MTMKCiMjIHJlZHJvY2sgKGFjdGlubyBwaGFnZSB3aXRoIFBhckFCUykgR1UzMzk0NjcsIHVzZXMgcGFyUyBzaXRlcyBidXQgb2YgYSBkaWZmZXJlbnQgdHlwZSB0aGFuIHNwb3J1bGF0aW9uIFBhclMKCiMjIGNvbWJpbmUgcGhhZ2UgbWV0YWRhdGEgd2l0aCBQYXJTIGhpdHMuIExhYmVsIGFueSBwaGFnZSB0aGF0IGhhZCBubyBQYXJTIGhpdHMgd2l0aCAibm9faGl0IiBpbiBtZXRhZGF0YQppbnBoIDwtIHJlYWQuY3N2KGhlcmUoIjAwX2RhdGEiLCAiaW5waGFyZWRfZGIiLCAiMTRBcHIyMDI1X2tub3duc3BvcmVzdGF0dXMuY3N2IiksIHJvdy5uYW1lcyA9IDEpCmlucGgkbGlmZXN0eWxlIDwtIGlmZWxzZShpbnBoJGxpZmVzdHlsZT09IlRlbXAiLCAiVGVtcGVyYXRlIiwgaW5waCRsaWZlc3R5bGUpCmlucGgkYmFjLmhvc3QgPC0gaWZlbHNlKGlucGgkc3BvcnVsYXRpb249PSJTcG9yIiwgIlNwb3J1bGF0aW5nIEJhY2lsbG90YSIsICJOb25zcG9ydWxhdGluZyBCYWNpbGxvdGEiKSAgIyJBc3Bvcm9nZW5vdXMgQmFjaWxsb3RhIikKaW5waCRiYWMuaG9zdCA8LSBpZmVsc2UoaW5waCRuZXdndGRiX1BoeWx1bT09IkJhY2lsbG90YSIsIGlucGgkYmFjLmhvc3QsICJOb24tQmFjaWxsb3RhIikKCmlucGggPC0gdW5pdGUoaW5waCwgbmljZSwgYygibGlmZXN0eWxlIiwgImJhYy5ob3N0IiksIHNlcD0iIFBoYWdlcyBvZiAiLCByZW1vdmUgPSBGQUxTRSApCgoKbWV0YS5jYXRzIDwtIHVuaXF1ZShzZWxlY3QoaW5waCwgaG9zdF9waGFnZV9zcG9yLCBiYWMuaG9zdCwgbGlmZXN0eWxlLCBuaWNlLCBwaGFnZV90eXBlLCBzcG9ydWxhdGlvbikpCgoKCmlucGggPC0gc2VsZWN0KGlucGgsIEFjY2Vzc2lvbiwgSG9zdCwgR2Vub21lLkxlbmd0aC4uYnAuLCBndGRiX2YsIGZfc3BvciwgbmV3Z3RkYl9QaHlsdW0sICBob3N0X3BoYWdlX3Nwb3IsIHBoYWdlX3R5cGUsIHNwb3J1bGF0aW9uLCBsaWZlc3R5bGUsIG5pY2UsIHNwb3J1bGF0aW9uKQphbGwgPC0gbWVyZ2UoaW5waCwgUGFyUywgYnkueD0iQWNjZXNzaW9uIiwgYnkueT0ic2VxdWVuY2VfbmFtZSIsIGFsbC54PVRSVUUsIGFsbC55PUZBTFNFKQphbGwkbW90aWZfaWRbaXMubmEoYWxsJG1vdGlmX2lkKV0gPC0gIm5vX2hpdCIKCiMjIyBjcmVhdGUgYmluYXJ5IGhpdCBjb2x1bW4gb2YgMSBmb3IgUGFyUyBoaXQsIDAgaWYgbm8gaGl0CmFsbCRoaXQgPC0gaWZlbHNlKGFsbCRtb3RpZl9pZD09IlBhclNfQlN1YiIsIDEsIDApCmFsbCRxLnZhbHVlW2lzLm5hKGFsbCRxLnZhbHVlKV0gPC0gMQoKIyMgc3Vic2V0IGZvciBKVVNUIEJBQ0lMTFVTIHNpbmNlIGkgdXNlZCBqdXN0IGEgYmFjaWxsdXMgcGFyUyBnZW5lCiNhbGwgPC0gc3Vic2V0KGFsbCwgYWxsJEhvc3Q9PSJCYWNpbGx1cyIpCiNhbGwgPC0gc3Vic2V0KGFsbCwgYWxsJG5ld2d0ZGJfUGh5bHVtID09IkJhY2lsbG90YSIpCgoKCgoKCmBgYAoKCmBgYHtyfQoKCiMjIyB0aHJlc2hvbGQgdGVzdGluZwoKbGlicmFyeShkcGx5cikKbGlicmFyeShnZ3Bsb3QyKQoKIyBEZWZpbmUgdGhyZXNob2xkcwp0aHJlc2hvbGRzIDwtIDEwXnNlcSgtNiwgLTEsIGJ5ID0gMSkgICAjIGZyb20gMWUtNiB0byAxZS0xCnRocmVzaG9sZHMgPC0gYyh0aHJlc2hvbGRzLCAwLjA1LCAxKSAgICAgICMgYWRkIDAuMDUgZXhwbGljaXRseSBpZiBkZXNpcmVkCnJlc3VsdHMgPC0gbGFwcGx5KHRocmVzaG9sZHMsIGZ1bmN0aW9uKHRoKSB7CiAgYWxsICU+JQogICAgZ3JvdXBfYnkoQWNjZXNzaW9uLCBwaGFnZV90eXBlKSAlPiUKICAgIHN1bW1hcmlzZSgKICAgICAgcG9zLlBhclMuaGl0ID0gbWF4KGlmZWxzZSghaXMubmEocS52YWx1ZSkgJiBxLnZhbHVlIDw9IHRoLCAxLCAwKSksCiAgICAgIC5ncm91cHMgPSAiZHJvcCIKICAgICkgJT4lCiAgICBncm91cF9ieShwaGFnZV90eXBlKSAlPiUKICAgIHN1bW1hcmlzZSgKICAgICAgUGhhZ2VfaGFzX1BhclMgPSBzdW0ocG9zLlBhclMuaGl0KSwKICAgICAgdG90YWwucGhhZ2UgPSBuKCksCiAgICAgIC5ncm91cHMgPSAiZHJvcCIKICAgICkgJT4lCiAgICBtdXRhdGUoCiAgICAgIFBhclMucG9zLnBlcmMgPSBQaGFnZV9oYXNfUGFyUyAvIHRvdGFsLnBoYWdlICogMTAwLAogICAgICB0aHJlc2hvbGQgPSB0aAogICAgKQp9KSAlPiUgYmluZF9yb3dzKCkKCnJlc3VsdHMuZmlnIDwtIG1lcmdlKHJlc3VsdHMsIG1ldGEuY2F0cywgYnk9InBoYWdlX3R5cGUiKQoKIyBQbG90CmdncGxvdChyZXN1bHRzLmZpZywgYWVzKHggPSB0aHJlc2hvbGQsIHkgPSBQYXJTLnBvcy5wZXJjLCBjb2xvciA9IHNwb3J1bGF0aW9uLCBsaW5ldHlwZSA9IGxpZmVzdHlsZSkpICsKICBnZW9tX2xpbmUoc2l6ZSA9IDEuMikgKwogIGdlb21fcG9pbnQoKSArCiAgc2NhbGVfeF9sb2cxMCgpICsgICMgbG9nIHNjYWxlIGZvciBxLXZhbHVlcwogIGxhYnMoCiAgICB4ID0gIkZhbHNlIFBvc2l0aXZlIChxLXZhbHVlKSBUaHJlc2hvbGQiLAogICAgeSA9ICIlIFBoYWdlcyB3aXRoIDEgb3IgbW9yZSBQYXJTIHNpdGUiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpICsgbGFicyhsaW5ldHlwZSA9ICJQaGFnZSBMaWZlc3R5bGUiLCBjb2xvcj0iQmFjdGVyaWFsIEhvc3QiKSArZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMWUtNCwgbGluZXR5cGUgPSAiZG90dGVkIikKCgoKCmBgYAoKCmBgYHtyfQoKIyMjIyBRIEZJTFRFUklORwpQYXJTLnF1YWwgPC0gYWxsCgpQYXJTLnF1YWwkaGl0IDwtIGlmZWxzZShQYXJTLnF1YWwkcS52YWx1ZTw9MWUtNCwgMSwgMCkKCgojIyBjcmVhdGUgYmluYXJ5IGxpc3Qgb2YgcGhhZ2VzIHcvIGFuZCB3L291dCBQYXJTIGhpdHMKUGFyUy5wb3MgPC0gUGFyUy5xdWFsICU+JSBncm91cF9ieShBY2Nlc3Npb24sIHBoYWdlX3R5cGUpICU+JQogIHN1bW1hcmlzZSh0b3RhbC5QYXJTLmhpdHMgPSBzdW0oaGl0KSwgcG9zLlBhclMuaGl0ID0gbWF4KGhpdCksIC5ncm91cHMgPSAiZHJvcCIpICMlPiUgClBhclMucG9zICU+JSBjb3VudChwaGFnZV90eXBlKQoKUGFyUy5zdW1tYXJ5IDwtIFBhclMucG9zICU+JSAKICBncm91cF9ieShwaGFnZV90eXBlKSAlPiUgCiAgc3VtbWFyaXNlKAogICAgdG90YWxfUGFyU19oaXRzID0gc3VtKHRvdGFsLlBhclMuaGl0cywgbmEucm0gPSBUUlVFKSwKICAgIFBoYWdlX2hhc19QYXJTID0gc3VtKHBvcy5QYXJTLmhpdCwgbmEucm0gPSBUUlVFKSwKICAgIHRvdGFsLnBoYWdlID0gbigpLAogICAgLmdyb3VwcyA9ICJkcm9wIgogICkgJT4lIAogIG11dGF0ZShQYXJTLnBvcy5wZXJjID0gUGhhZ2VfaGFzX1BhclMgLyB0b3RhbC5waGFnZSAqIDEwMCkKCgoKCmBgYAoKCgoKCgoKCmBgYHtyfQoKCiNQYXJTLmhpdHMgPC0gUGFyUy5oaXRzLmFsbApQYXJTLmhpdHMgPC0gc3Vic2V0KFBhclMucXVhbCwgUGFyUy5xdWFsJGhpdD09MSkKI1BhclMuaGl0cyA8LSBzdWJzZXQoUGFyUy5oaXRzLmFsbCwgUGFyUy5oaXRzLmFsbCRwaGFnZV90eXBlPT0iTHl0aWNfU3BvciIpCgojIyBmaW5kIGNlbnRlciBwaGFnZSBnZW5vbWUgKHdob2xlIGdlbm9tZSAvMikKUGFyUy5oaXRzJHNlcS5tZHB0IDwtIGFzLm51bWVyaWMoUGFyUy5oaXRzJEdlbm9tZS5MZW5ndGguLmJwLikvMgoKIyMgZmluZCBjZW50ZXIgb2YgbW90aWYgClBhclMuaGl0cyRtb3RpZi5tZHB0IDwtIChQYXJTLmhpdHMkc3RvcCArIFBhclMuaGl0cyRzdGFydCApLyAyCgojIyMgc3VidHJhY3QgbWlkcG9pbnQgbW90aWYgZnJvbSBzZXF1ZW5jZSBtaWRwb2ludCB0byBzZWUgaG93IGZhciBhd2F5IHRoZXkgYXJlClBhclMuaGl0cyA8LSBQYXJTLmhpdHMgJT4lCiAgbXV0YXRlKG1kcHQuYWxpZ24gPSAobW90aWYubWRwdCAtIHNlcS5tZHB0KSkKClBhclMuaGl0cyRtZHB0LmFsaWduLmticCA8LSBQYXJTLmhpdHMkbWRwdC5hbGlnbi8xMDAwCgojIyMjIFRPIEdFVCBSRUxBVElWRSBtb3RpZiBhbGlnbm1lbgoKIyBSZWxhdGl2ZSBwb3NpdGlvbiBhcyBmcmFjdGlvbiBvZiBnZW5vbWUgbGVuZ3RoCiMgKC0wLjUgPSBzdGFydCwgMCA9IGNlbnRlciwgKzAuNSA9IGVuZCkKUGFyUy5oaXRzJHJlbC5tZHB0IDwtIChQYXJTLmhpdHMkbW90aWYubWRwdCAtIFBhclMuaGl0cyRzZXEubWRwdCkgLyBQYXJTLmhpdHMkR2Vub21lLkxlbmd0aC4uYnAuCgoKCiMgUmVsYXRpdmUgcG9zaXRpb24gZnJvbSBnZW5vbWUgc3RhcnQgKDAgdG8gMSkKUGFyUy5oaXRzJHJlbC5mcmFjIDwtIFBhclMuaGl0cyRtb3RpZi5tZHB0IC8gUGFyUy5oaXRzJEdlbm9tZS5MZW5ndGguLmJwLgoKIyBPcHRpb25hbGx5IGNvbnZlcnQgdG8gcGVyY2VudGFnZQpQYXJTLmhpdHMkcmVsLnBlcmNlbnQgPC0gUGFyUy5oaXRzJHJlbC5mcmFjICogMTAwCgojI1NldCBzcGVjaWZpYyBvcmRlciBmb3IgYmFjdGVyaWFsIGhvc3RzIHRvIGFwcGVhciBvbiBncmFwaHMKUGFyUy5oaXRzJG5pY2UgPC0gZmFjdG9yKFBhclMuaGl0cyRuaWNlLCBsZXZlbHMgPSBjKCdMeXRpYyBQaGFnZXMgb2YgU3BvcnVsYXRpbmcgQmFjaWxsb3RhJywgJ1RlbXBlcmF0ZSBQaGFnZXMgb2YgU3BvcnVsYXRpbmcgQmFjaWxsb3RhJywgJ0x5dGljIFBoYWdlcyBvZiBOb25zcG9ydWxhdGluZyBCYWNpbGxvdGEnLCAnVGVtcGVyYXRlIFBoYWdlcyBvZiBOb25zcG9ydWxhdGluZyBCYWNpbGxvdGEnLCAnTHl0aWMgUGhhZ2VzIG9mIE5vbi1CYWNpbGxvdGEnLCAnVGVtcGVyYXRlIFBoYWdlcyBvZiBOb24tQmFjaWxsb3RhJyApLG9yZGVyZWQgPSBUUlVFKQoKCmdncGxvdChQYXJTLmhpdHMsIGFlcyh4ID0gbWRwdC5hbGlnbi5rYnAsIGZpbGwgPSBwaGFnZV90eXBlKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gNSwgcG9zaXRpb24gPSAiZG9kZ2UiKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgbGluZXR5cGUgPSAiZG90dGVkIikgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoLTkwLCA5MCwgYnkgPSAzMCkpICsKICBsYWJzKHggPSAiTW90aWYgZGlzdGFuY2UgKGtiKSBmcm9tIGNlbnRlciBvZiBwaGFnZSBnZW5vbWUiLCB5ID0gIk1vdGlmIGNvdW50IikgKwogIGZhY2V0X3dyYXAofiBwaGFnZV90eXBlLCBuY29sID0gMikrIGdndGl0bGUoIkFic29sdXRlIFBvc2l0aW9uIG9mIFBhclMgTW90aWYgb24gUGhhZ2UgR2Vub21lcyIpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikKCmdnc2F2ZShoZXJlKCIwMl9tb3RpZnMvUGFyUy9BYnNvbHV0ZVBhclNwb3NpdGlvbi5wbmciKSkKCmdncGxvdChQYXJTLmhpdHMsIGFlcyh4ID0gcmVsLm1kcHQsIGZpbGwgPSBwaGFnZV90eXBlKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC4wNSwgcG9zaXRpb24gPSAiZG9kZ2UiKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgbGluZXR5cGUgPSAiZG90dGVkIikgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoLTAuNSwgMC41LCBieSA9IDAuMjUpKSArCiAgbGFicyh4ID0gIk1vdGlmIHBvc2l0aW9uIHJlbGF0aXZlIHRvIGNlbnRlciBvZiBwaGFnZSBnZW5vbWUiLCB5ID0gIk1vdGlmIGNvdW50IikgKwogIGZhY2V0X3dyYXAofiBwaGFnZV90eXBlLCBuY29sID0gMikgKyBnZ3RpdGxlKCJSZWxhdGl2ZSBQb3NpdGlvbiBvZiBQYXJTIE1vdGlmIG9uIFBoYWdlIEdlbm9tZXMiKSsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikKCmdnc2F2ZShoZXJlKCIwMl9tb3RpZnMvUGFyUy9SZWxhdGl2ZVBhclNwb3NpdGlvbi5wbmciKSkKYGBgCnN0YXRpc3RpY3MgCmBgYHtyfQoKUGFyUy5iaW4gPC0gUGFyUy5wb3NbLGMoMSwyLDQpXQoKCgpgYGAKCmBgYHtyfQphbmFseXplX2VucmljaG1lbnQgPC0gZnVuY3Rpb24oZGYsIHJlZl90cmVhdG1lbnQgPSAiRW5yaWNoZWQiKSB7CiAgIyByZWxldmVsIHRoZSB0cmVhdG1lbnQgZmFjdG9yCiAgZGYkaG9zdF9waGFnZV9zcG9yIDwtIHJlbGV2ZWwoZmFjdG9yKGRmJHBoYWdlX3R5cGUpLCByZWYgPSByZWZfdHJlYXRtZW50KQogIAogICMgbG9naXN0aWMgcmVncmVzc2lvbjogbW90aWYgcHJlc2VuY2UgfiB0cmVhdG1lbnQKICBtb2RlbCA8LSBnbG0ocG9zLlBhclMuaGl0IH4gcGhhZ2VfdHlwZSwgZmFtaWx5ID0gYmlub21pYWwsIGRhdGEgPSBkZikKICAKICAjIGVzdGltYXRlZCBwcm9iYWJpbGl0aWVzIHBlciB0cmVhdG1lbnQKICBlbW0gPC0gZW1tZWFucyhtb2RlbCwgfiBwaGFnZV90eXBlLCB0eXBlID0gInJlc3BvbnNlIikKICAKICBsaXN0KAogICAgbW9kZWxfc3VtbWFyeSA9IHN1bW1hcnkobW9kZWwpLAogICAgcHJvYmFiaWxpdGllcyA9IGVtbSwKICAgIHBhaXJ3aXNlX3Rlc3RzID0gcGFpcnMoZW1tLCBhZGp1c3QgPSAidHVrZXkiKQogICkKfQpsaWJyYXJ5KGVtbWVhbnMpCnJlcyA8LSBhbmFseXplX2VucmljaG1lbnQoUGFyUy5iaW4sIHJlZl90cmVhdG1lbnQgPSAiTHl0aWNfU3BvciIpCgpyZXMkbW9kZWxfc3VtbWFyeSAgICAgIyBsb2dpc3RpYyByZWdyZXNzaW9uIGNvZWZmaWNpZW50cwpyZXMkcHJvYmFiaWxpdGllcyAgICAgIyBlc3RpbWF0ZWQgbW90aWYgcHJvYmFiaWxpdHkgcGVyIHRyZWF0bWVudApyZXMkcGFpcndpc2VfdGVzdHMgICAgIyBhbGwgcGFpcndpc2UgY29tcGFyaXNvbnMKCgpwcm9iX2RmIDwtIGFzLmRhdGEuZnJhbWUocmVzJHByb2JhYmlsaXRpZXMpCmhlYWQocHJvYl9kZikKCnByb2JfZGYkaG9zdF9waGFnZV9zcG9yIDwtIHJlb3JkZXIocHJvYl9kZiRwaGFnZV90eXBlLCBwcm9iX2RmJHByb2IpCgoKCmZpZy5tZXRhIDwtIG1lcmdlKHByb2JfZGYsIG1ldGEuY2F0cywgYnk9InBoYWdlX3R5cGUiLCBhbGw9VFJVRSkKCgoKYGBgCgoKCmBgYHtyfQpwZCA8LSBwb3NpdGlvbl9kb2RnZSh3aWR0aCA9IDAuNikgICMgYWRqdXN0IHdpZHRoIGFzIG5lZWRlZAoKZ2dwbG90KGZpZy5tZXRhLCBhZXMoeCA9IHBoYWdlX3R5cGUsIHkgPSBwcm9iLCBjb2xvciA9IHNwb3J1bGF0aW9uKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDMsIHBvc2l0aW9uID0gcGQpICsgIHlsaW0oMCwwLjIwKSsKICBnZW9tX2Vycm9yYmFyKGFlcyh5bWluID0gYXN5bXAuTENMLCB5bWF4ID0gYXN5bXAuVUNMKSwgCiAgICAgICAgICAgICAgICB3aWR0aCA9IDAuMiwgcG9zaXRpb24gPSBwZCkgKwogIHlsYWIoIlByb2JhYmlsaXR5IG9mIDEgb3IgbW9yZSBQYXJTIFxuIGJpbmRpbmcgc2l0ZXMgaW4gcGhhZ2UgZ2Vub21lIikgKwogIHhsYWIoIkJhY3RlcmlhbCBIb3N0IikgKwogIGxhYnMoY29sb3IgPSAiUGhhZ2UgTGlmZXN0eWxlIikgKwogIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpICsKICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKHJldmVyc2UgPSBGQUxTRSkpCgoKCgoKZ2dwbG90KGZpZy5tZXRhLCBhZXMoeCA9IHBoYWdlX3R5cGUsIHkgPSBwcm9iLCBjb2xvciA9IGxpZmVzdHlsZSkpICsKICAgZ2VvbV9wb2ludChzaXplID0gMywgcG9zaXRpb24gPSBwZCkgKyAgeWxpbSgwLDAuMjApKwogIGdlb21fZXJyb3JiYXIoYWVzKHltaW4gPSBhc3ltcC5MQ0wsIHltYXggPSBhc3ltcC5VQ0wpLCAKICAgICAgICAgICAgICAgIHdpZHRoID0gMC4yLCBwb3NpdGlvbiA9IHBkKSsKICB5bGFiKCJQcm9iYWJpbGl0eSBvZiAxIG9yIG1vcmUgUGFyUyBcbiBiaW5kaW5nIHNpdGVzIGluIHBoYWdlIGdlbm9tZSIpICsKICB4bGFiKCJCYWN0ZXJpYWwgSG9zdCIpICsKICBsYWJzKGNvbG9yID0gIlBoYWdlIExpZmVzdHlsZSIpICsKICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE0KSArCiAgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZChyZXZlcnNlID0gRkFMU0UpKQoKYGBgCgpgYGB7cn0KCiMjIGNyZWF0ZSBiaW5hcnkgbGlzdCBvZiBwaGFnZXMgdy8gYW5kIHcvb3V0IFBhclMgaGl0cwpQYXJTLnN1bSA8LSBhbGwgJT4lIGdyb3VwX2J5KEFjY2Vzc2lvbiwgcGhhZ2VfdHlwZSkgJT4lCiAgc3VtbWFyaXNlKHRvdGFsLlBhclMuaGl0cyA9IHN1bShoaXQpLCBwb3MuUGFyUy5oaXQgPSBtYXgoaGl0KSwgLmdyb3VwcyA9ICJkcm9wIikgIyU+JSAKCgpnZ3Bsb3QoUGFyUy5wb3MsIGFlcyh4ID0gZmFjdG9yKHBoYWdlX3R5cGUpLCBmaWxsID0gZmFjdG9yKHBoYWdlX3R5cGUpLCBjb2xvcj1mYWN0b3IocGhhZ2VfdHlwZSksIHkgPSB0b3RhbC5QYXJTLmhpdHMgKSkgKwogIGdlb21fYm94cGxvdChiaW5heGlzID0gInkiLCBzdGFja2RpciA9ICJjZW50ZXIiLCBwb3NpdGlvbiA9ICJkb2RnZSIpICsgIGdlb21faml0dGVyKHdpZHRoID0gMC4yKSArIHlsYWIoIk51bWJlciBvZiBQYXJTIHNpdGVzIC8gR2Vub21lIikgK3RoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNTAsIHZqdXN0ID0gMSwgaGp1c3QgPSAxKSkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibGVmdCIpICsgeGxhYigiUGh5bGFfU3Bvcl9MaWZlc3R5bGUiKSArIGdndGl0bGUoIlRvdGFsIFBhclMgQmluZGluZyBTaXRlcyBwZXIgR2Vub21lIikKCmdncGxvdChQYXJTLnBvcywgYWVzKHggPSBmYWN0b3IocGhhZ2VfdHlwZSksIGZpbGwgPSBmYWN0b3IocGhhZ2VfdHlwZSksIGNvbG9yPWZhY3RvcihwaGFnZV90eXBlKSwgeSA9IHRvdGFsLlBhclMuaGl0cyApKSArIGdlb21fdmlvbGluKCkgIysgIGdlb21faml0dGVyKHdpZHRoID0gMC4yKSArIHlsYWIoIk51bWJlciBvZiBQYXJTIHNpdGVzIC8gR2Vub21lIikgK3RoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNTAsIHZqdXN0ID0gMSwgaGp1c3QgPSAxKSkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibGVmdCIpICsgeGxhYigiUGh5bGFfU3Bvcl9MaWZlc3R5bGUiKSArIGdndGl0bGUoIlRvdGFsIFBhclMgQmluZGluZyBTaXRlcyBwZXIgR2Vub21lIikKCgoKUGFyU19zdW1tYXJ5IDwtIFBhclMucG9zICU+JQogIGdyb3VwX2J5KHBoYWdlX3R5cGUpICU+JQogIHN1bW1hcmlzZShtZWFuX2hpdCA9IG1lYW4ocG9zLlBhclMuaGl0LCBuYS5ybSA9IFRSVUUpKjEwMCwKICAgICAgICAgICAgc2VfaGl0ID0gKHNkKHBvcy5QYXJTLmhpdCwgbmEucm0gPSBUUlVFKS9zcXJ0KG4oKSkpKQoKUGFyU19zdW1tYXJ5JHNlX2hpdCA8LSBQYXJTX3N1bW1hcnkkc2VfaGl0KjEwMAoKZ2dwbG90KFBhclNfc3VtbWFyeSwgYWVzKHggPSBmYWN0b3IocGhhZ2VfdHlwZSksCiAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gbWVhbl9oaXQsCiAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gZmFjdG9yKHBoYWdlX3R5cGUpKSkgKwogIGdlb21fY29sKGNvbG9yID0gImJsYWNrIiwgcG9zaXRpb24gPSAiZG9kZ2UiKSArIAogIGdlb21fZXJyb3JiYXIoYWVzKHltaW4gPSBtZWFuX2hpdCAtIHNlX2hpdCwKICAgICAgICAgICAgICAgICAgICB5bWF4ID0gbWVhbl9oaXQgKyBzZV9oaXQpLAogICAgICAgICAgICAgICAgd2lkdGggPSAwLjIpICsKICB5bGFiKCIlIFBoYWdlIHdpdGggMSsgUGFyUyAgc2l0ZSIpICsKICB4bGFiKCJQaHlsYV9TcG9yX0xpZmVzdHlsZSIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDUwLCB2anVzdCA9IDEsIGhqdXN0ID0gMSksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArIGdndGl0bGUoIlByb3BvcnRpb24gb2YgUGhhZ2VzIHdpdGggYXQgbGVhc3QgMSBQYXJTIEJpbmRpbmcgU2l0ZSIpCgoKCmdncGxvdChQYXJTLnBvcywgYWVzKHggPSBmYWN0b3IocGhhZ2VfdHlwZSksIGZpbGwgPSBmYWN0b3IocGhhZ2VfdHlwZSksIGNvbG9yPWZhY3RvcihwaGFnZV90eXBlKSwgeSA9IHBvcy5QYXJTLmhpdCApKSArCiAgZ2VvbV9ib3hwbG90KGJpbmF4aXMgPSAieSIsIHN0YWNrZGlyID0gImNlbnRlciIsIHBvc2l0aW9uID0gImRvZGdlIikgKyAgZ2VvbV9qaXR0ZXIod2lkdGggPSAwLjIpICsgeWxhYigiTnVtYmVyIG9mIFBhclMgc2l0ZXMgLyBHZW5vbWUiKSArdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA1MCwgdmp1c3QgPSAxLCBoanVzdCA9IDEpKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJsZWZ0IikgKyB4bGFiKCJQaHlsYV9TcG9yX0xpZmVzdHlsZSIpICsgZ2d0aXRsZSgiVG90YWwgUGFyUyBCaW5kaW5nIFNpdGVzIHBlciBHZW5vbWUiKQoKCmdncGxvdChQYXJTLnBvcywgYWVzKHggPSBmYWN0b3IocGhhZ2VfdHlwZSksIGZpbGwgPSBmYWN0b3IocGhhZ2VfdHlwZSksIGNvbG9yPWZhY3RvcihwaGFnZV90eXBlKSwgeSA9IHBvcy5QYXJTLmhpdCApKSArCiAgZ2VvbV92aW9saW4oKSArIGdlb21faml0dGVyKHdpZHRoID0gMC4yKSArIHlsYWIoIk51bWJlciBvZiBQYXJTIHNpdGVzIC8gR2Vub21lIikgK3RoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNTAsIHZqdXN0ID0gMSwgaGp1c3QgPSAxKSkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibGVmdCIpICsgeGxhYigiUGh5bGFfU3Bvcl9MaWZlc3R5bGUiKSArIGdndGl0bGUoIlRvdGFsIFBhclMgQmluZGluZyBTaXRlcyBwZXIgR2Vub21lIikKCgpnZ3Bsb3QoUGFyUy5wb3MsIGFlcyh4ID0gZmFjdG9yKHBoYWdlX3R5cGUpLCBmaWxsID0gZmFjdG9yKHBoYWdlX3R5cGUpLCBjb2xvcj1mYWN0b3IocGhhZ2VfdHlwZSksIHkgPSBwb3MuUGFyUy5oaXQgKSkgKwogIGdlb21fdmlvbGluKCkKYGBgCgpgYGB7cn0KcGFpcndpc2VfZmlzaGVyX3N1bW1hcnkgPC0gZnVuY3Rpb24oZGYsIGdyb3VwX2NvbCA9ICJncm91cCIsIGhpdHNfY29sID0gImhpdHMiLCB0b3RhbF9jb2wgPSAidG90YWwiLCBwLmFkanVzdC5tZXRob2QgPSAiQkgiKSB7CiAgZ3JvdXBzIDwtIGRmW1tncm91cF9jb2xdXQogIGNvbWJzIDwtIGNvbWJuKGdyb3VwcywgMiwgc2ltcGxpZnkgPSBGQUxTRSkKICAKICByZXN1bHRzIDwtIGRhdGEuZnJhbWUoCiAgICBncm91cDEgPSBjaGFyYWN0ZXIoKSwKICAgIGdyb3VwMiA9IGNoYXJhY3RlcigpLAogICAgb2Rkc19yYXRpbyA9IG51bWVyaWMoKSwKICAgIGNvbmZfbG93ID0gbnVtZXJpYygpLAogICAgY29uZl9oaWdoID0gbnVtZXJpYygpLAogICAgcC52YWx1ZSA9IG51bWVyaWMoKSwKICAgIHAuYWRqID0gbnVtZXJpYygpLAogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCiAgKQogIAogIHB2YWxzIDwtIG51bWVyaWMoKQogIAogIGZvciAoYyBpbiBjb21icykgewogICAgIyBzdWJzZXQgdGhlIHR3byBncm91cHMKICAgIGcxIDwtIGRmW2RmW1tncm91cF9jb2xdXSA9PSBjWzFdLCBdCiAgICBnMiA8LSBkZltkZltbZ3JvdXBfY29sXV0gPT0gY1syXSwgXQogICAgCiAgICAjIGNyZWF0ZSAyeDIgdGFibGUKICAgIHRhYiA8LSBtYXRyaXgoYygKICAgICAgZzFbW2hpdHNfY29sXV0sIGcxW1t0b3RhbF9jb2xdXSAtIGcxW1toaXRzX2NvbF1dLAogICAgICBnMltbaGl0c19jb2xdXSwgZzJbW3RvdGFsX2NvbF1dIC0gZzJbW2hpdHNfY29sXV0KICAgICksIG5yb3cgPSAyLCBieXJvdyA9IFRSVUUpCiAgICAKICAgIHJvd25hbWVzKHRhYikgPC0gYyhjWzFdLCBjWzJdKQogICAgY29sbmFtZXModGFiKSA8LSBjKCJQcmVzZW50IiwgIkFic2VudCIpCiAgICAKICAgIGZ0IDwtIGZpc2hlci50ZXN0KHRhYikKICAgIAogICAgcHZhbHMgPC0gYyhwdmFscywgZnQkcC52YWx1ZSkKICAgIAogICAgcmVzdWx0cyA8LSByYmluZChyZXN1bHRzLCBkYXRhLmZyYW1lKAogICAgICBncm91cDEgPSBjWzFdLAogICAgICBncm91cDIgPSBjWzJdLAogICAgICBvZGRzX3JhdGlvID0gZnQkZXN0aW1hdGUsICAgICAgICAgICAgICMgb2RkcyByYXRpbwogICAgICBjb25mX2xvdyA9IGZ0JGNvbmYuaW50WzFdLCAgICAgICAgICAgICMgbG93ZXIgOTUlIENJCiAgICAgIGNvbmZfaGlnaCA9IGZ0JGNvbmYuaW50WzJdLCAgICAgICAgICAgIyB1cHBlciA5NSUgQ0kKICAgICAgcC52YWx1ZSA9IGZ0JHAudmFsdWUsCiAgICAgIHAuYWRqID0gTkEKICAgICkpCiAgfQogIAogICMgQWRqdXN0IHAtdmFsdWVzCiAgcmVzdWx0cyRwLmFkaiA8LSBwLmFkanVzdChwdmFscywgbWV0aG9kID0gcC5hZGp1c3QubWV0aG9kKQogIAogIHJldHVybihyZXN1bHRzKQp9CgoKI1BhcnMuRklTSCwgCgp0IDwtIHBhaXJ3aXNlX2Zpc2hlcl9zdW1tYXJ5KAogIGRmID0gUGFyUy5zdW1tYXJ5LAogIGdyb3VwX2NvbCA9ICJwaGFnZV90eXBlIiwgCiAgaGl0c19jb2wgPSAiUGhhZ2VfaGFzX1BhclMiLCAgIyBudW1iZXIgb2YgcG9zaXRpdmUgaGl0cwogIHRvdGFsX2NvbCA9ICJ0b3RhbC5waGFnZSIsICAgICMgdG90YWwgbnVtYmVyIHBlciBncm91cAogIHAuYWRqdXN0Lm1ldGhvZCA9ICJCSCIKKQoKdAoKdCRzaWcgPC0gRkFMU0UKCnQkc2lnIDwtaWZlbHNlKHQkcC5hZGo8MC4wNSwgVFJVRSwgRkFMU0UpCmBgYAoKCmBgYHtyfQoKCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyAx77iP4oOjIFByZXBhcmUgcGxvdHRpbmcgZGF0YWZyYW1lCnByb2JfZGYgPC0gYXMuZGF0YS5mcmFtZShyZXMkcHJvYmFiaWxpdGllcykKCiNtZXRhLmNhdHMgPC0gbWV0YS5jYXRzWyxjKDUsNildCgpmaWcubWV0YSA8LSBtZXJnZShwcm9iX2RmLCBtZXRhLmNhdHMsIGJ5ID0gInBoYWdlX3R5cGUiLCBhbGwgPSBUUlVFKQoKIyBTZXQgY3VzdG9tIHgtYXhpcyBvcmRlcgpmaWcubWV0YSRwaGFnZV90eXBlIDwtIGZhY3RvcigKICBmaWcubWV0YSRwaGFnZV90eXBlLAogIGxldmVscyA9IGMoCiAgICAjJ0x5dGljX1Nwb3InLCAnTHl0aWNfTm9uU3BvcicsICdUZW1wX1Nwb3InLCAnVGVtcF9Ob25TcG9yJwogICAgJ0x5dGljX1Nwb3InLCAnVGVtcF9TcG9yJywnTHl0aWNfTm9uU3BvcicsICAnVGVtcF9Ob25TcG9yJwogICksCiAgb3JkZXJlZCA9IFRSVUUKKQoKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIDLvuI/ig6MgRXh0cmFjdCBwYWlyd2lzZSBUdWtleSB0ZXN0cwpgYGAKcGFpcnNfZGYgPC0gYXMuZGF0YS5mcmFtZShyZXMkcGFpcndpc2VfdGVzdHMpICU+JQogIG11dGF0ZSgKICAgIGNvbnRyYXN0X2NoYXIgPSBhcy5jaGFyYWN0ZXIoY29udHJhc3QpLAogICAgcF9sYWJlbCA9IGNhc2Vfd2hlbigKICAgICAgcC52YWx1ZSA8IDAuMDAxIH4gIioqKiIsCiAgICAgIHAudmFsdWUgPCAwLjAxICB+ICIqKiIsCiAgICAgIHAudmFsdWUgPCAwLjA1ICB+ICIqIiwKICAgICAgVFJVRSAgICAgICAgICAgIH4gIm5zIgogICAgKSwKICAgIGdyb3VwMSA9IHNhcHBseShzdHJzcGxpdChjb250cmFzdF9jaGFyLCAiIC8gIiksIGBbYCwgMSksCiAgICBncm91cDIgPSBzYXBwbHkoc3Ryc3BsaXQoY29udHJhc3RfY2hhciwgIiAvICIpLCBgW2AsIDIpCiAgKQpgYGB7cn0KIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIDPvuI/ig6MgQ29tcHV0ZSB4IHBvc2l0aW9ucyBhbmQgc3BhbgpwYWlyc19kZiA8LSBhcy5kYXRhLmZyYW1lKHJlcyRwYWlyd2lzZV90ZXN0cykgJT4lCiAgbXV0YXRlKAogICAgY29udHJhc3RfY2hhciA9IGFzLmNoYXJhY3Rlcihjb250cmFzdCksCiAgICBwX2xhYmVsID0gY2FzZV93aGVuKAogICAgICBwLnZhbHVlIDwgMC4wMDEgfiAiKioqIiwKICAgICAgcC52YWx1ZSA8IDAuMDEgIH4gIioqIiwKICAgICAgcC52YWx1ZSA8IDAuMDUgIH4gIioiLAogICAgICBUUlVFICAgICAgICAgICAgfiAibnMiCiAgICApLAogICAgZ3JvdXAxID0gc2FwcGx5KHN0cnNwbGl0KGNvbnRyYXN0X2NoYXIsICIgLyAiKSwgYFtgLCAxKSwKICAgIGdyb3VwMiA9IHNhcHBseShzdHJzcGxpdChjb250cmFzdF9jaGFyLCAiIC8gIiksIGBbYCwgMikKICApCgpwYWlyc19kZiA8LSBhcy5kYXRhLmZyYW1lKHJlcyRwYWlyd2lzZV90ZXN0cykgJT4lCiAgbXV0YXRlKAogICAgY29udHJhc3RfY2hhciA9IGFzLmNoYXJhY3Rlcihjb250cmFzdCksCiAgICBwX2xhYmVsID0gY2FzZV93aGVuKAogICAgICBwLnZhbHVlID4gMC4wNSAgICAgICAgICAgIH4gIm5zIiwgICAgICAgICAgICAgICAgICAgICAgIyBub24tc2lnbmlmaWNhbnQKICAgICAgcC52YWx1ZSA8IDAuMDAxICAgICAgICAgICB+ICJwIDwgMC4wMDAxIiwgICAgICAgICAgICAgICMgdmVyeSBzbWFsbCBwLXZhbHVlcwogICAgICBUUlVFICAgICAgICAgICAgICAgICAgICAgICB+IHBhc3RlMCgicCA9ICIsIHNwcmludGYoIiUuM2YiLCBwLnZhbHVlKSkgICMgb3RoZXJzCiAgICApLAogICAgZ3JvdXAxID0gc2FwcGx5KHN0cnNwbGl0KGNvbnRyYXN0X2NoYXIsICIgLyAiKSwgYFtgLCAxKSwKICAgIGdyb3VwMiA9IHNhcHBseShzdHJzcGxpdChjb250cmFzdF9jaGFyLCAiIC8gIiksIGBbYCwgMikKICApCgoKCgpwYWlyc19kZiA8LSBwYWlyc19kZiAlPiUKICBtdXRhdGUoCiAgICB4X251bTEgPSBhcy5udW1lcmljKGZhY3Rvcihncm91cDEsIGxldmVscyA9IGxldmVscyhmaWcubWV0YSRwaGFnZV90eXBlKSkpLAogICAgeF9udW0yID0gYXMubnVtZXJpYyhmYWN0b3IoZ3JvdXAyLCBsZXZlbHMgPSBsZXZlbHMoZmlnLm1ldGEkcGhhZ2VfdHlwZSkpKSwKICAgIHNwYW4gPSBhYnMoeF9udW0xIC0geF9udW0yKSwKICAgIHhfcG9zID0gKHhfbnVtMSArIHhfbnVtMikgLyAyCiAgKSAlPiUKICBhcnJhbmdlKHNwYW4pCgojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgNO+4j+KDoyBDb21wdXRlIHkgcG9zaXRpb25zIGZvciBuZXN0ZWQgYnJhY2tldHMKb2Zmc2V0X3N0ZXAgPC0gMC4wMQoKcGFpcnNfZGYgPC0gcGFpcnNfZGYgJT4lCiAgcm93d2lzZSgpICU+JQogIG11dGF0ZSgKICAgIHlfYmFzZSA9IG1heCgKICAgICAgZmlnLm1ldGEkYXN5bXAuVUNMW2ZpZy5tZXRhJHBoYWdlX3R5cGUgJWluJSBjKGdyb3VwMSwgZ3JvdXAyKV0sCiAgICAgIG5hLnJtID0gVFJVRQogICAgKQogICkgJT4lCiAgdW5ncm91cCgpICU+JQogIGFycmFuZ2Uoc3BhbiwgZGVzYyhwX2xhYmVsKSkgJT4lICAjIHNob3J0ZXIgc3BhbnMgbG93ZXIsIG5zIGxvd2VyCiAgbXV0YXRlKAogICAgeV9wb3MgPSB5X2Jhc2UgKyAocm93X251bWJlcigpIC0gMSkgKiBvZmZzZXRfc3RlcAogICkKCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyA177iP4oOjIFByZXBhcmUgY29tcGFyaXNvbnMgbGlzdCBmb3IgZ2dzaWduaWYKY29tcGFyaXNvbnNfbGlzdCA8LSBsYXBwbHkoMTpucm93KHBhaXJzX2RmKSwgZnVuY3Rpb24oaSkgewogIGMoYXMuY2hhcmFjdGVyKHBhaXJzX2RmJGdyb3VwMVtpXSksIGFzLmNoYXJhY3RlcihwYWlyc19kZiRncm91cDJbaV0pKQp9KQoKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIDbvuI/ig6MgUGxvdCB3aXRoIGRvZGdlIGFuZCBibGFjayBicmFja2V0cwpwZCA8LSBwb3NpdGlvbl9kb2RnZSh3aWR0aCA9IDAuNSkKCnRwbG90IDwtIGdncGxvdChmaWcubWV0YSwgYWVzKHggPSBwaGFnZV90eXBlLCB5ID0gcHJvYiwgY29sb3IgPSBsaWZlc3R5bGUpKSArCiAgZ2VvbV9wb2ludChzaXplID0gMywgcG9zaXRpb24gPSBwZCkgKwogIGdlb21fZXJyb3JiYXIoYWVzKHltaW4gPSBhc3ltcC5MQ0wsIHltYXggPSBhc3ltcC5VQ0wpLCB3aWR0aCA9IDAuMiwgcG9zaXRpb24gPSBwZCkgKwogIGdnc2lnbmlmOjpnZW9tX3NpZ25pZigKICAgIGNvbXBhcmlzb25zID0gY29tcGFyaXNvbnNfbGlzdCwKICAgIGFubm90YXRpb25zID0gcGFpcnNfZGYkcF9sYWJlbCwKICAgIHlfcG9zaXRpb24gPSBwYWlyc19kZiR5X3BvcywKICAgIHRpcF9sZW5ndGggPSAwLjAyLAogICAgdGV4dHNpemUgPSAzLjUsCiAgICBjb2xvciA9ICJibGFjayIKICApICsKICB5bGltKDAsIG1heChwYWlyc19kZiR5X3BvcyArIDAuMDIpKSArICAjIGV4dGVuZCB5LWF4aXMgdG8gZml0IHRvcCBicmFja2V0cwogICB5bGFiKCJQcm9iYWJpbGl0eSBvZiAxIG9yIG1vcmUgUGFyUyBcbiBiaW5kaW5nIHNpdGVzIGluIHBoYWdlIGdlbm9tZSIpICsKICB4bGFiKCIiKSArCiAgbGFicyhjb2xvciA9ICJQaGFnZSBMaWZlc3R5bGUiKSArCiAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNCkrc2NhbGVfeF9kaXNjcmV0ZSgKICAgIGxhYmVscyA9IGMoCiAgICAgICdMeXRpY19TcG9yJyAgICA9ICdMeXRpYycsCiAgICAgICdUZW1wX1Nwb3InICAgICA9ICdUZW1wZXJhdGUnLAogICAgICAnTHl0aWNfTm9uU3BvcicgPSAnTHl0aWMnLAogICAgICAnVGVtcF9Ob25TcG9yJyAgPSAnVGVtcGVyYXRlJwogICAgKQogICkgKyB0aGVtZShwbG90Lm1hcmdpbiA9IG1hcmdpbigxMCwgMTAsIDMwLCAxMCkgICMgbGFyZ2UgYm90dG9tIG1hcmdpbiBmb3IgbGFiZWxzCiAgKSArCiAgY29vcmRfY2FydGVzaWFuKGNsaXAgPSAib2ZmIikKCnRwbG90CgoKc3Bvcl9sYWJlbCA8LSB0ZXh0R3JvYigKICAiU3BvcnVsYXRpbmcgSG9zdCIsIGdwID0gZ3Bhcihmb250c2l6ZSA9IDE0LCBmb250ZmFjZSA9ICJib2xkIiksCiAgeCA9IDAuMjUsIHkgPSAtMC4xMywganVzdCA9ICJjZW50ZXIiCikKCm5vbnNwb3JfbGFiZWwgPC0gdGV4dEdyb2IoCiAgIk5vbi1TcG9ydWxhdGluZyBIb3N0IiwgZ3AgPSBncGFyKGZvbnRzaXplID0gMTQsIGZvbnRmYWNlID0gImJvbGQiKSwKICB4ID0gMC43NSwgeSA9IC0wLjEzLCBqdXN0ID0gImNlbnRlciIKKQoKCnRwbG90X2ZpbmFsIDwtIHRwbG90ICsKICBhbm5vdGF0aW9uX2N1c3RvbShzcG9yX2xhYmVsKSArCiAgYW5ub3RhdGlvbl9jdXN0b20obm9uc3Bvcl9sYWJlbCkKCnRwbG90X2ZpbmFsCgoKYGBgCgpgYGB7cn0KCnBhaXJzX2RmMiA8LSBzdWJzZXQocGFpcnNfZGYsIHBhaXJzX2RmJGNvbnRyYXN0PT0iTHl0aWNfTm9uU3BvciAvIEx5dGljX1Nwb3IiIHwKICAgICAgICAgICAgICAgICAgICAgIHBhaXJzX2RmJGNvbnRyYXN0PT0gIlRlbXBfTm9uU3BvciAvIFRlbXBfU3BvciIgfAogICAgICAgICAgICAgICAgICAgICAgcGFpcnNfZGYkY29udHJhc3Q9PSJCYWNpbGxvdGFfTHl0aWNfTm9uU3BvciAvIEJhY2lsbG90YV9UZW1wX05vblNwb3IiIHwKICAgICAgICAgICAgICAgICAgICAgIHBhaXJzX2RmJGNvbnRyYXN0PT0iTHl0aWNfU3BvciAvIFRlbXBfU3BvciIgfAogICAgICAgICAgICAgICAgICAgICAgcGFpcnNfZGYkY29udHJhc3Q9PSJMeXRpY19Ob25TcG9yIC8gVGVtcF9Ob25TcG9yIikKI0JhY2lsbG90YV9MeXRpY19TcG9yIC8gT3RoZXJQaHlsYV9MeXRpY19Ob25TcG9yCgpwYWlyc19kZjIgPC0gcGFpcnNfZGYyICU+JQogIG11dGF0ZSgKICAgIHhfbnVtMSA9IGFzLm51bWVyaWMoZmFjdG9yKGdyb3VwMSwgbGV2ZWxzID0gbGV2ZWxzKGZpZy5tZXRhJHBoYWdlX3R5cGUpKSksCiAgICB4X251bTIgPSBhcy5udW1lcmljKGZhY3Rvcihncm91cDIsIGxldmVscyA9IGxldmVscyhmaWcubWV0YSRwaGFnZV90eXBlKSkpLAogICAgc3BhbiA9IGFicyh4X251bTEgLSB4X251bTIpLCAgIyBkaXN0YW5jZSBiZXR3ZWVuIGdyb3VwcwogICAgeF9wb3MgPSAoeF9udW0xICsgeF9udW0yKSAvIDIgICMgY2VudGVyIGZvciBicmFja2V0CiAgKSAlPiUKICBhcnJhbmdlKHNwYW4pICAjIHNtYWxsZXIgc3BhbnMgZmlyc3QKCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyA077iP4oOjIENvbXB1dGUgeSBwb3NpdGlvbnMgZm9yIG5lc3RlZCBicmFja2V0cwoKcGFpcnNfZGYyIDwtIHBhaXJzX2RmMiAlPiUKICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKAogICAgeV9iYXNlID0gbWF4KAogICAgICBmaWcubWV0YSRhc3ltcC5VQ0xbZmlnLm1ldGEkcGhhZ2VfdHlwZSAlaW4lIGMoZ3JvdXAxLCBncm91cDIpXSwKICAgICAgbmEucm0gPSBUUlVFCiAgICApCiAgKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgbXV0YXRlKAogICAgeV9wb3MgPSB5X2Jhc2UgKyAocm93X251bWJlcigpIC0gMSkgKiBvZmZzZXRfc3RlcCAgIyBzdGFjayBieSByb3cgb3JkZXIgKHNtYWxsZXIgc3BhbiBsb3dlcikKICApCgojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgNe+4j+KDoyBQcmVwYXJlIGNvbXBhcmlzb25zIGxpc3QgZm9yIGdnc2lnbmlmCmNvbXBhcmlzb25zX2xpc3QgPC0gbGFwcGx5KDE6bnJvdyhwYWlyc19kZjIpLCBmdW5jdGlvbihpKSB7CiAgYyhhcy5jaGFyYWN0ZXIocGFpcnNfZGYyJGdyb3VwMVtpXSksIGFzLmNoYXJhY3RlcihwYWlyc19kZjIkZ3JvdXAyW2ldKSkKfSkKCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyA277iP4oOjIFBsb3Qgd2l0aCBkb2RnZSBmb3IgbXVsdGlwbGUgaG9zdHMKcGQgPC0gcG9zaXRpb25fZG9kZ2Uod2lkdGggPSAwLjUpICAjIGFkanVzdCB3aWR0aCBmb3Igc2VwYXJhdGlvbiBvZiBwb2ludHMKCnRwbG90MiA8LSBnZ3Bsb3QoZmlnLm1ldGEsIGFlcyh4ID0gcGhhZ2VfdHlwZSwgeSA9IHByb2IsIGNvbG9yID0gbGlmZXN0eWxlKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDMsIHBvc2l0aW9uID0gcGQpICsKICBnZW9tX2Vycm9yYmFyKGFlcyh5bWluID0gYXN5bXAuTENMLCB5bWF4ID0gYXN5bXAuVUNMKSwgd2lkdGggPSAwLjIsIHBvc2l0aW9uID0gcGQpICsKICBnZ3NpZ25pZjo6Z2VvbV9zaWduaWYoCiAgICBjb21wYXJpc29ucyA9IGNvbXBhcmlzb25zX2xpc3QsCiAgICBhbm5vdGF0aW9ucyA9IHBhaXJzX2RmMiRwX2xhYmVsLAogICAgeV9wb3NpdGlvbiA9IHBhaXJzX2RmMiR5X3BvcywKICAgIHRpcF9sZW5ndGggPSAwLjAyLAogICAgdGV4dHNpemUgPSAzLjUsCiAgICBjb2xvciA9ICJibGFjayIKICApICsKICB5bGltKDAsIG1heChwYWlyc19kZjIkeV9wb3MgKyAwLjAyKSkgKyAgIyBleHRlbmQgeS1heGlzIHRvIGZpdCB0b3AgYnJhY2tldHMKICAgeWxhYigiUHJvYmFiaWxpdHkgb2YgMSBvciBtb3JlIFBhclMgXG4gYmluZGluZyBzaXRlcyBpbiBwaGFnZSBnZW5vbWUiKSArCiAgeGxhYigiIikgKwogIGxhYnMoY29sb3IgPSAiUGhhZ2UgTGlmZXN0eWxlIikgKwogIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpK3NjYWxlX3hfZGlzY3JldGUoCiAgICBsYWJlbHMgPSBjKAogICAgICAnTHl0aWNfU3BvcicgICAgPSAnTHl0aWMnLAogICAgICAnVGVtcF9TcG9yJyAgICAgPSAnVGVtcGVyYXRlJywKICAgICAgJ0x5dGljX05vblNwb3InID0gJ0x5dGljJywKICAgICAgJ1RlbXBfTm9uU3BvcicgID0gJ1RlbXBlcmF0ZScKICAgICkKICApICsgdGhlbWUocGxvdC5tYXJnaW4gPSBtYXJnaW4oMTAsIDEwLCAzMCwgMTApICAjIGxhcmdlIGJvdHRvbSBtYXJnaW4gZm9yIGxhYmVscwogICkgKwogIGNvb3JkX2NhcnRlc2lhbihjbGlwID0gIm9mZiIpCgp0cGxvdDIKCgpzcG9yX2xhYmVsIDwtIHRleHRHcm9iKAogICJTcG9ydWxhdGluZyBIb3N0IiwgZ3AgPSBncGFyKGZvbnRzaXplID0gMTQsIGZvbnRmYWNlID0gImJvbGQiKSwKICB4ID0gMC4yNSwgeSA9IC0wLjEzLCBqdXN0ID0gImNlbnRlciIKKQoKbm9uc3Bvcl9sYWJlbCA8LSB0ZXh0R3JvYigKICAiTm9uLVNwb3J1bGF0aW5nIEhvc3QiLCBncCA9IGdwYXIoZm9udHNpemUgPSAxNCwgZm9udGZhY2UgPSAiYm9sZCIpLAogIHggPSAwLjc1LCB5ID0gLTAuMTMsIGp1c3QgPSAiY2VudGVyIgopCgoKdHBsb3RfZmluYWwyIDwtIHRwbG90MiArCiAgYW5ub3RhdGlvbl9jdXN0b20oc3Bvcl9sYWJlbCkgKwogIGFubm90YXRpb25fY3VzdG9tKG5vbnNwb3JfbGFiZWwpCgp0cGxvdF9maW5hbDIKCmBgYAoK